<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="Ubunj" id="Ubunj"><span data-lake-id="u499d3c5f" id="u499d3c5f">典型回答</span></h1>
  <p data-lake-id="u5a595101" id="u5a595101"><br></p>
  <p data-lake-id="uab49b663" id="uab49b663"><span data-lake-id="u77ae2634" id="u77ae2634">可重入锁是一种多线程同步机制，允许</span><strong><span data-lake-id="u5610013a" id="u5610013a">同一线程</span></strong><span data-lake-id="uee5f174a" id="uee5f174a">多次获取同一个锁而不会导致死锁。</span></p>
  <p data-lake-id="ucf8735a2" id="ucf8735a2"><span data-lake-id="uf9993011" id="uf9993011">​</span><br></p>
  <p data-lake-id="u18897206" id="u18897206"><br></p>
  <p data-lake-id="ubc125eb6" id="ubc125eb6"><span data-lake-id="ue0e92c6f" id="ue0e92c6f">在Redis中，最简单的方式就是使用setnx来实现一个分布式锁了，但是如果我想要实现一个具有重入功能的锁，那么用setnx如何实现呢？</span></p>
  <p data-lake-id="u03223806" id="u03223806"><br></p>
  <p data-lake-id="u40d1a262" id="u40d1a262"><span data-lake-id="u91080246" id="u91080246">首先，我们需要有一个标识来识别出一个线程，这里可以是线程ID，分布式的traceId（</span><a href="https://www.yuque.com/hollis666/vhr2ge/nnl88aqknhx2v76c" data-lake-id="u6cc8b612" id="u6cc8b612"><span data-lake-id="u5159b8b7" id="u5159b8b7">https://www.yuque.com/hollis666/vhr2ge/nnl88aqknhx2v76c</span></a><span data-lake-id="u883688d4" id="u883688d4"> ），或者是一个唯一的业务ID都可以。</span></p>
  <p data-lake-id="u51ae5f9b" id="u51ae5f9b"><span data-lake-id="uf399d123" id="uf399d123">​</span><br></p>
  <p data-lake-id="u0995491c" id="u0995491c"><span data-lake-id="ubd1d3112" id="ubd1d3112">有了这个唯一标识之后，我们加锁的时候，就可以用这个标识来判断当前持有锁的线程是不是自己，如果是的话，就可以直接重入。否则就无法重入。</span></p>
  <p data-lake-id="udfb5bb05" id="udfb5bb05"><span data-lake-id="u73f1b4b7" id="u73f1b4b7">​</span><br></p>
  <p data-lake-id="ub5890a0b" id="ub5890a0b"><span data-lake-id="u424ea2df" id="u424ea2df">为了保证重入几次之后，需要同时解锁几次，那么我们也需要维护一个重入次数的字段。因为每一次重入其实就是一个加锁动作，避免出现加锁2次，但是1次解锁动作就把锁给解了的情况。</span></p>
  <p data-lake-id="u1fd84780" id="u1fd84780"><span data-lake-id="u88b31cc6" id="u88b31cc6" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">​</span><br></p>
  <p data-lake-id="uda2b2949" id="uda2b2949"><span data-lake-id="u96e23a17" id="u96e23a17" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">有了以上基础之后，加锁和解锁的逻辑如下：</span></p>
  <p data-lake-id="u2645c724" id="u2645c724"><strong><span data-lake-id="u2f62da08" id="u2f62da08" class="lake-fontsize-12" style="color: var(--tw-prose-bold)">​</span></strong><br></p>
  <p data-lake-id="ucd464dae" id="ucd464dae"><strong><span data-lake-id="u0faae8f7" id="u0faae8f7" class="lake-fontsize-12" style="color: var(--tw-prose-bold)">加锁的逻辑</span></strong><span data-lake-id="ua4ad7cb0" id="ua4ad7cb0" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">：</span></p>
  <ul list="u2cfe12b1" data-lake-indent="1">
   <li fid="u4d67a998" data-lake-id="u32aa6a38" id="u32aa6a38"><span data-lake-id="uab113432" id="uab113432" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">当线程尝试获取锁时，它首先检查锁是否已经存在。</span></li>
   <li fid="u4d67a998" data-lake-id="uf000ff54" id="uf000ff54"><span data-lake-id="u8f2254a2" id="u8f2254a2" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">如果锁不存在（即 </span><strong><span data-lake-id="ufb897a79" id="ufb897a79" class="lake-fontsize-12" style="color: var(--tw-prose-code)">SETNX</span></strong><span data-lake-id="u9db1354c" id="u9db1354c" class="lake-fontsize-12" style="color: rgb(55, 65, 81)"> 返回成功），线程设置锁，存储自己的标识符和计数器（初始化为1）。</span></li>
   <li fid="u4d67a998" data-lake-id="u91df706c" id="u91df706c"><span data-lake-id="u7bb72ac0" id="u7bb72ac0" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">如果锁已存在，线程检查锁中的标识符是否与自己的相同。</span></li>
  </ul>
  <ul list="u2cfe12b1" data-lake-indent="2">
   <li fid="u58ce3d88" data-lake-id="u13ac8472" id="u13ac8472"><span data-lake-id="ue59a6cff" id="ue59a6cff" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">如果是，线程已经持有锁，只需增加计数器的值。</span></li>
   <li fid="u58ce3d88" data-lake-id="u82377596" id="u82377596"><span data-lake-id="u4982ecb2" id="u4982ecb2" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">如果不是，获取锁失败，因为锁已被其他线程持有。</span></li>
  </ul>
  <p data-lake-id="uf8705785" id="uf8705785"><br></p>
  <p data-lake-id="u48556d55" id="u48556d55"><strong><span data-lake-id="ud53ef537" id="ud53ef537" class="lake-fontsize-12" style="color: var(--tw-prose-bold)">解锁的逻辑</span></strong><span data-lake-id="ub2d36cad" id="ub2d36cad" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">：</span></p>
  <ul list="u695548be" data-lake-indent="1">
   <li fid="u35fe1775" data-lake-id="u72bbb995" id="u72bbb995"><span data-lake-id="u946ad7fb" id="u946ad7fb" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">当线程释放锁时，它会减少计数器的值。</span></li>
   <li fid="u35fe1775" data-lake-id="u2f5272a9" id="u2f5272a9"><span data-lake-id="uebe0344d" id="uebe0344d" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">如果计数器降至0，这意味着线程已完成对锁的所有获取请求，可以完全释放锁。</span></li>
   <li fid="u35fe1775" data-lake-id="uf1c482aa" id="uf1c482aa"><span data-lake-id="ue53953ae" id="ue53953ae" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">如果计数器大于0，锁仍被视为被该线程持有。</span></li>
  </ul>
  <p data-lake-id="u48bc3319" id="u48bc3319"><br></p>
  <p data-lake-id="u2e1e90e9" id="u2e1e90e9"><span data-lake-id="uc9af2ffa" id="uc9af2ffa" class="lake-fontsize-12">代码实现如下：</span></p>
  <p data-lake-id="uf33a7ac4" id="uf33a7ac4"><span data-lake-id="u7e17374b" id="u7e17374b" class="lake-fontsize-12">​</span><br></p>
  <pre lang="java"><code>
import redis.clients.jedis.Jedis;

public class ReentrantRedisLock {

    public synchronized boolean tryLock(Jedis jedis,String lockKey) {
        String currentThreadId = String.valueOf(Thread.currentThread().getId());

        // 尝试获取锁
        String lockValue = jedis.get(lockKey);
        if (lockValue == null) {
            // 锁不存在，尝试设置锁
            jedis.set(lockKey, currentThreadId + ":1", "NX", "EX", 30);
            return true;
        }

        // 锁存在，检查是否由当前线程持有
        String[] parts = lockValue.split(":");

        //加锁线程是当前线程，则增加次数，进行重入加锁
        if (parts.length == 2 &amp;&amp; parts[0].equals(currentThreadId)) {
            int count = Integer.parseInt(parts[1]) + 1;
            jedis.set(lockKey, currentThreadId + ":" + count, "XX", "EX", 30);
            return true;
        }

        //加锁失败
        return false;
    }

    public synchronized void unlock(Jedis jedis,String lockKey) {
        String currentThreadId = String.valueOf(Thread.currentThread().getId());

        String lockValue = jedis.get(lockKey);
        if (lockValue != null) {
            String[] parts = lockValue.split(":");
            if (parts.length == 2 &amp;&amp; parts[0].equals(currentThreadId)) {
                int count = Integer.parseInt(parts[1]);
                //减少重入次数
                if (count &gt; 1) {
                    jedis.set(lockKey, currentThreadId + ":" + (count - 1), "XX", "EX", 30);
                } else {
                    //解锁
                    jedis.del(lockKey);
                }
            }
        }
    }
}

</code></pre>
  <p data-lake-id="u7e9b8256" id="u7e9b8256"><br></p>
  <p data-lake-id="ubc7162a3" id="ubc7162a3"><span data-lake-id="u8b42c526" id="u8b42c526" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">在这个实现中，锁的值是一个由线程 ID 和锁的获取次数组成的字符串，格式为 </span><code data-lake-id="uecc64c48" id="uecc64c48"><span data-lake-id="ue310a8bc" id="ue310a8bc" style="color: var(--tw-prose-code)">线程ID:次数</span></code><span data-lake-id="u75ee85d2" id="u75ee85d2" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">。当一个线程尝试获取锁时，它会检查当前的锁值。</span></p>
  <p data-lake-id="u3f647b98" id="u3f647b98"><span data-lake-id="ubf6f9cda" id="ubf6f9cda" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">​</span><br></p>
  <p data-lake-id="ua95edbff" id="ua95edbff"><span data-lake-id="uf2ab40f7" id="uf2ab40f7" class="lake-fontsize-12" style="color: rgb(55, 65, 81)">如果锁由相同的线程持有，则增加计数器；否则，尝试设置新的锁。释放锁时，它会递减计数器，当计数器为零时，锁被完全释放。</span></p>
 </body>
</html>